`timescale 1ns/100ps
`default_nettype none

`define FEOF 32'hFFFFFFFF
`define MAX_MISMATCHES 10

// file for output
`define OUTPUT_FILE_NAME "motorcycle_tb.ppm"

// file for comparison 
`define COMPARE_FILE_NAME "motorcycle.ppm"

`define INPUT_FILE_NAME "motorcycle.d1"

`define LOG_FILE_NAME "YUVRGB.log"

module tb_InC;

logic Clock_50;
logic [17:0] Switches;
logic [3:0] Push_buttons;
logic [8:0] LED_Green;
logic [6:0] seven_segment_n [7:0];

wire [15:0] SRAM_data_io;
logic [15:0] SRAM_write_data, SRAM_read_data;
logic [17:0] SRAM_address_o;
logic [17:0] SRAM_address;
logic SRAM_we;
logic SRAM_UB_N;
logic SRAM_LB_N;
logic SRAM_WE_N;
logic SRAM_CE_N;
logic SRAM_OE_N;

logic SRAM_resetn;
logic SRAM_ready;

integer output_file;

// Instantiate the unit under test
InC uut (
    .CLOCK_50_I(Clock_50),             // 50 MHz Clock
    .resetn(~Switches[17]),                 // Reset
    .start(~Push_buttons[1]),               // Start signal
    .SRAM_Data(SRAM_read_data),         // Data read from SRAM
    
    .SRAM_Write(SRAM_write_data),         // Data to write to SRAM
    .SRAM_Addr(SRAM_address),        // SRAM address
    .we(SRAM_we),                    // SRAM write enable
    .finish(LED_Green[0])              // Finish signal
);

// The emulator for the external SRAM during simulation
tb_SRAM_Emulator SRAM_component (
    .Clock_50(Clock_50),
    .Resetn(SRAM_resetn),
    
    .SRAM_data_io(SRAM_data_io),
    .SRAM_address(SRAM_address_o),
    .SRAM_UB_N(SRAM_UB_N),
    .SRAM_LB_N(SRAM_LB_N),
    .SRAM_WE_N(SRAM_WE_N),
    .SRAM_CE_N(SRAM_CE_N),
    .SRAM_OE_N(SRAM_OE_N)
);

// SRAM unit
SRAM_Controller SRAM_unit (
    .Clock_50(Clock_50),
    .Resetn(~Switches[17]),
    .SRAM_address(SRAM_address),
    .SRAM_write_data(SRAM_write_data),
    .SRAM_we_n(~SRAM_we),
    .SRAM_read_data(SRAM_read_data),        
    .SRAM_ready(SRAM_ready),
        
    // To the SRAM pins
    .SRAM_DATA_IO(SRAM_data_io),
    .SRAM_ADDRESS_O(SRAM_address_o),
    .SRAM_UB_N_O(SRAM_UB_N),
    .SRAM_LB_N_O(SRAM_LB_N),
    .SRAM_WE_N_O(SRAM_WE_N),
    .SRAM_CE_N_O(SRAM_CE_N),
    .SRAM_OE_N_O(SRAM_OE_N)
);

// Generate a 50 MHz clock
always begin
    # 10;
    Clock_50 = ~Clock_50;
end

// Task for generating master reset
task master_reset;
begin
    wait (Clock_50 !== 1'bx);
    @ (posedge Clock_50);
    $write("Applying global reset...\n\n");
    Switches[17] = 1'b1;
    // Activate reset for 2 clock cycles
    @ (posedge Clock_50);
    @ (posedge Clock_50);   
    Switches[17] = 1'b0;    
    $write("Removing global reset...\n\n"); 
end
endtask

// Task for filling the SRAM directly to shorten simulation time
task fill_SRAM;
    integer uart_file, file_data, temp, i, new_line_count;
    logic [15:0] buffer;
begin
    $write("Opening file \"%s\" for initializing SRAM\n\n", `INPUT_FILE_NAME);
    uart_file = $fopen(`INPUT_FILE_NAME, "rb");
    file_data = $fgetc(uart_file);
    i = 0;
    while (file_data != `FEOF) begin
        buffer[15:8] = file_data & 8'hFF;
        file_data = $fgetc(uart_file);          
        buffer[7:0] = file_data & 8'hFF;
        SRAM_component.SRAM_data[i] = buffer;
        i++;

        file_data = $fgetc(uart_file);
    end

    $fclose(uart_file);

    $write("Finish initializing SRAM\n\n");
end
endtask

// Initialize signals
initial begin
    // This is for setting the time format
    $timeformat(-3, 2, " ms", 10);
    
    $write("Simulation started at %t\n\n", $realtime);
    Clock_50 = 1'b0;
    Switches = 18'd0;
    SRAM_resetn = 1'b1;
    output_file = $fopen(`LOG_FILE_NAME, "wb");
    
    // Apply master reset
    master_reset;
    Push_buttons = 4'hF;
    
    @ (posedge Clock_50);
    // Clear SRAM
    SRAM_resetn = 1'b0;
    
    @ (posedge Clock_50);
    SRAM_resetn = 1'b1;
    
    @ (posedge Clock_50);
    @ (posedge Clock_50);   

    fill_SRAM;
    $write("SRAM is now filled at %t\n\n", $realtime);

    Push_buttons[1] = 1'b0; 
    wait (uut.state != 0);
    $write("Starting Decoder at %t\n\n", $realtime);
    Push_buttons[1] = 1'b1;
    
    wait (uut.finish == 1);
    $write("Decoding finished at %t\n\n", $realtime);
    $fclose(output_file);
    $stop;
end

// Monitor YUV and RGB data
logic [15:0] column;
logic [15:0] column_plus_one;
logic [7:0] Y_reg_buf;
logic we_buf;
logic cscOStart_delay;
logic cscOStart_delay2;
logic cscOStart_delay3;
logic cscEStart_delay;
logic cscEStart_delay2;
logic cscEStart_delay3;
logic [7:0] row_buf;
assign column_plus_one = column + 15'd1;
always @ (posedge Clock_50) begin
    we_buf <= uut.we;
    cscOStart_delay <= uut.cscOStart;
    cscOStart_delay2 <= cscOStart_delay;
    cscOStart_delay3 <= cscOStart_delay2;
    cscEStart_delay <= uut.cscEStart;
    cscEStart_delay2 <= cscEStart_delay;
    cscEStart_delay3 <= cscEStart_delay2;
    row_buf <= uut.row;
    // Reset column count when row changed
    if (row_buf!=uut.row || uut.start) begin
        column <= 15'b0;
        Y_reg_buf <= 8'b0;
    end
    
    // Write out Y_reg[0] UR[2] and VR[2], these are the non interpolated U and V
    // Do this 3 clock after interpolation start
    if (cscEStart_delay3==1) begin
        $fwrite(output_file, "YUV_E\t%d,%d\t %x, %x, %x\n", uut.row, column, uut.Y_reg[0], uut.UR[2], uut.VR[2]);
        Y_reg_buf <= uut.Y_reg[1];  // Buffer Y_reg[1] for odd YUV
    end
    
    // Write out Y_reg[1] U_out_buf and V_out_buf, these are the interpolated U and V
    // Do this one clock after cscOStart is 1
    if (cscOStart_delay==1) begin
        $fwrite(output_file, "RGB_E\t%d,%d\t %x, %x, %x\n", uut.row, column, uut.R_out_buf[15:8], uut.G_out_buf[15:8], uut.B_out_buf[15:8]);
        $fwrite(output_file, "YUV_O\t%d,%d\t %x, %x, %x\n", uut.row, column_plus_one, Y_reg_buf, uut.U_out_buf[7:0], uut.V_out_buf[7:0]);
    end
    
    // Write out RGB values
    // Do this 2 clock after cscOStart is 1
    if (cscOStart_delay3==1) begin
        $fwrite(output_file, "RGB_O\t%d,%d\t %x, %x, %x\n", uut.row, column_plus_one, uut.R_out_buf[7:0], uut.G_out_buf[7:0], uut.B_out_buf[7:0]);
        column <= column + 15'd2;
    end
        
end

endmodule
